package ia;
import entidades.*;

import java.util.Map;
import java.util.List;
import java.util.HashMap;
import java.sql.SQLException;

/**
 * Esta implementacion busca que la suma de todos los newlongs de '0'
 * @author JFM
 */

public class Distribucion implements Comparable
	{
	// ----------------------------------------- //
	// ----- Variables y metodos estaticos ----- //
	// ----------------------------------------- //

	// Vector de formatos.
	private static Dimensiones[] formatos;
	// Vector de newlongs.
	private static Newlong[] newlongs;
	// Mapa que permite obtener el indice (para el vector 'formatos') de un formato a partir de su id.
	private static Map<Integer,Integer> indicesFormatos;
	// Mapa que permite obtener el indice (para el vector 'newlongs') de un newlong a partir de su id.
	private static Map<Integer,Integer> indicesNewlongs;

	// Matriz (newlongs x formatos) que indica que formatos puede imprimir cada newlong.
	private static boolean[][] capacidades;
	// Vector (newlongs) que indica que cantidad de formatos puede imprimir cada newlong.
	private static int[] soporteNewlongs;
	// Vector (formatos) que indica que cantidad de newlongs pueden imprimir a un formato.
	private static int[] soporteFormatos;

	// Vector (formatos) que indica que la proporcion (en porcentaje) de la demanda para cada formato.
	private static float[] demandaFormatos;

	// Valor de la funcion de fitness.
	private float fitness;

	// Inicializacion de todas las variables estaticas.
	static
		{
		try{
			List<Newlong> newlongsAux = Newlong.listar();
			newlongs = new Newlong[newlongsAux.size()];
			indicesNewlongs = new HashMap();
			soporteNewlongs = new int[newlongs.length];
			for(int i=0; i<newlongs.length; i++)
				{
				newlongs[i] = newlongsAux.get(i);
				indicesNewlongs.put(newlongsAux.get(i).getId(), i);
				soporteNewlongs[i] = 0;
				}

			List<Dimensiones> formatosAux = Dimensiones.listar();
			formatos = new Dimensiones[formatosAux.size()];
			indicesFormatos = new HashMap();
			soporteFormatos = new int[formatos.length];
			for(int i=0; i<formatos.length; i++)
				{
				formatos[i] = formatosAux.get(i);
				indicesFormatos.put(formatosAux.get(i).getId(), i);
				soporteFormatos[i] = 0;
				}

			capacidades = new boolean[newlongs.length][formatos.length];
			for(int n=0; n<newlongs.length; n++)
				{
				for(int f=0; f<formatos.length; f++)
					{ capacidades[n][f] = false; }
				}

			List<FormatosPorNewlong> capacidadesAux = FormatosPorNewlong.listar();
			for(int i=0; i<capacidadesAux.size(); i++)
				{
				Integer iF = indicesFormatos.get(capacidadesAux.get(i).getFormatoId());
				Integer iN = indicesNewlongs.get(capacidadesAux.get(i).getNewlongId());
				if(iF != null && iN != null)
					{
					soporteNewlongs[iN]++;
					soporteFormatos[iF]++;
					capacidades[iN][iF] = true;
					}
				}

			demandaFormatos = new float[formatos.length];
			// Creo una demanda ficticia para poder hacer pruebas.
			for(int f=0; f<formatos.length; f++)
				{ demandaFormatos[f] = 1.0f * newlongs.length / formatos.length; }
			}catch(SQLException e) {}
		}

	// Establece la demanda para generar las distribuciones.
	public static void setDemanda(Map<Integer,Float> demand)
		{
		// Traduzo el mapa (que posee el Id del formato junto con su porcentaje) a un vector, cuyo indice coincide con los indices del vector de formatos.
		for(int i=0; i<formatos.length; i++)
			{
			demandaFormatos[i] = (demand.get(formatos[i].getId()) != null)? demand.get(formatos[i].getId()) : 0.0f;
			demandaFormatos[i] *= (float) newlongs.length / 100.0f;
			}
		}





	// --------------------------------------------- //
	// ----- Variables y metodos de instancias ----- //
	// --------------------------------------------- //

	// Matriz (newlongs x formatos) que indica que porcentaje de tiempo dedica cada newlong a un formato.
	private float distribucion[][];



	// Creo una distribucion aleatorio.
	public Distribucion()
		{
		distribucion = generarDistribucion();
		optimizar();
		calcularFitness();
		}

	// Creo una distribucion a partir de otra (mutacion).
	public Distribucion(Distribucion d)
		{
		distribucion = mutarDistribucion(d.distribucion);
		optimizar();
		calcularFitness();
		}

	// Creo una distribucion a partir de otras dos (cruce).
	public Distribucion(Distribucion d1, Distribucion d2)
		{
		distribucion = cruzarDistribuciones(d1.distribucion, d2.distribucion);
		optimizar();
		calcularFitness();
		}



	// La solucion es mejor mientras mas proximo a cero es su valor de fitness.
	public float getFitness()
		{ return fitness; }

	// La funcion de fitness es la que mide que tan buena es esta solucion.
	private void calcularFitness()
		{
		fitness = 0.0f;

		for(int f=0; f<formatos.length; f++ )
			{
			float suma = 0.0f;
			for(int n=0; n<newlongs.length; n++)
				{ suma += distribucion[n][f]; }
			suma -= demandaFormatos[f];

			fitness += suma*suma;
			}
		}

	// Mejora el fitness y se asegura que la suma de todos los newlongs es de 1.0.
	private void optimizar()
		{
		// Normalizo antes de empezar.
		normalizar();

		// Realizo la optimizacion varias veces.
		for(int h=0; h<4; h++)
			{
			// Mejoro el fitness.
			equilibrar();

			// Normalizo.
			normalizar();
			}
		}

	private void equilibrar()
		{
		float[] ofertaFormatos = calcularOferta();

		for(int n=0; n<newlongs.length; n++)
			{
			for(int f1=0; f1<formatos.length; f1++)
				{
				// Verifico que el newlong pueda confeccionar el formato.
				if(capacidades[n][f1])
					{
					int f2 = 0;

					// Recorro todos los formatos de newlong para ver si puedo hacer una transferencia, desde un formato cualquiera al formato 'f1'.
					while(ofertaFormatos[f1] < demandaFormatos[f1] - 0.5f && f2 < formatos.length)
						{
					
						// Verifico que:
						// - 'f1' es distinto de 'f2'.
						// - El newlong 'n' puede confeccionar el formato 'f2'.
						// - La oferta de 'f2' supera a la demanda.
						// - El newlnog 'n' tiene asignado tiempo para el formato 'f2'.
						if(f2 != f1 && capacidades[n][f2] && ofertaFormatos[f2] > demandaFormatos[f2] && distribucion[n][f2] > 0.0001f)
							{
							float diferencia = (ofertaFormatos[f2] - demandaFormatos[f2]) * newlongs.length;
							float cambio = (distribucion[n][f2] > diferencia)? diferencia : distribucion[n][f2];

							distribucion[n][f2] -= cambio;
							distribucion[n][f1] += cambio;
							ofertaFormatos[f2] -= cambio;
							ofertaFormatos[f1] += cambio;
							}
						f2++;
						}

					// Fin del 'if' que verifico si el newlong puede confeccionar al formato.
					}
				// Fin del 'for' que recorre los formatos.
				}
			// Fin del 'for' que recorre los newlong.
			}
		}

	private void normalizar()
		{
		for(int n=0; n<newlongs.length; n++)
			{
			// Normalizo.
			float suma = 0.0f;

			for(int f=0; f<formatos.length; f++ )
				{ suma += distribucion[n][f]; }

			if(suma < 0.99f || suma > 1.01f)
				{
				for(int f=0; f<formatos.length; f++ )
					{ distribucion[n][f] /= suma;}
				}
			}
		}

	private float[] calcularOferta()
		{
		float[] sumas = new float[formatos.length];

		for(int f=0; f<formatos.length; f++)
			{
			for(int n=0; n<newlongs.length; n++)
				{ sumas[f] += distribucion[n][f]; }
			}

		return sumas;
		}

	public int compareTo(Object o)
		{
		Distribucion d = (Distribucion) o;
		if(this.fitness == d.fitness) return 0;
		if(this.fitness > d.fitness) return 1;
		return -1;
		}

	public void rehacer()
		{
		generarDistribucion(distribucion);
		optimizar();
		calcularFitness();
		}

	public void mutar(Distribucion d)
		{
		mutarDistribucion(distribucion, d.distribucion);
		optimizar();
		calcularFitness();
		}

	public void cruzar(Distribucion d1, Distribucion d2)
		{
		cruzarDistribuciones(distribucion, d1.distribucion, d2.distribucion);
		optimizar();
		calcularFitness();
		}

	// Genera una distribucion aleatoria.
	private static float[][] generarDistribucion()
		{
		float[][] distribucionAleatoria = new float[newlongs.length][formatos.length];
		generarDistribucion(distribucionAleatoria);
		return distribucionAleatoria;
		}

	private static void generarDistribucion(float[][] distribucionAleatoria)
		{
		for(int n=0; n<newlongs.length; n++)
			{
			float total = 1.0f;
			int soporte = soporteNewlongs[n];

			// Hago la asignacion para los formatos soportados por una sola newlong.
			for(int f=0; f<formatos.length; f++)
				{
				// Verifico si el formato solo es soportado por una sola newlong.
				if(soporteFormatos[f] <= 1)
					{
					// Verifico si esta newlong puede confeccionar el formato.
					if(capacidades[n][f])
						{
						float asig = ( demandaFormatos[f] <= total )? demandaFormatos[f] : total;

						distribucionAleatoria[n][f] = asig;
						total -= asig;
						soporte--;
						}
					else
						{ distribucionAleatoria[n][f] = 0.0f; }
					}
				}

			// Hago la asignacion para los formatos soportados por varias newlong.
			if(soporte >= 0)
				{
				// Hago un vector con asignaciones aleatorias para las newlongs restantes.
				float[] asignaciones = new float[soporte];
				float auxT = 0.0f;
				for(int i=0; i<asignaciones.length; i++)
					{
					asignaciones[i] = (float) Math.random();

					auxT += asignaciones[i];
					}
				for(int i=0; i<asignaciones.length; i++)
					{
					// Si todos los valores aleatorios dieron '0', simplemente hago una division en partes iguales.
					if(auxT <= 0.1f)
						{ asignaciones[i] = total/asignaciones.length; }
					else
						{ asignaciones[i] *= total/auxT; }
					}

				// Hago la asignacion para los formatos restantes con el vector de valores aleatorios.
				int v = 0;
				for(int f=0; f<formatos.length; f++)
					{
					// Verifico si el formato solo es soportado por una sola newlong.
					if(soporteFormatos[f] > 1)
						{
						// Verifico si esta newlong puede confeccionar el formato.
						if(capacidades[n][f])
							{
							float asig = asignaciones[v];
							v++;

							distribucionAleatoria[n][f] = asig;
							total -= asig;
							soporte--;
							}
						else
							{ distribucionAleatoria[n][f] = 0.0f; }
						}
					}
				}

			// Fin del lazo de newlongs.
			}
		}


	// Creo una distribucion a partir de otra (mutacion).
	private static float[][] mutarDistribucion(float[][] original)
		{
		float[][] mutante = generarDistribucion();
		mutarDistribucion(mutante, original);
		return mutante;
		}

	private static void mutarDistribucion(float[][] mutante, float[][] original)
		{
		float ran = (float) Math.random();
		if(ran < 0.4f) ran = 0.4f;

		for(int n=0; n<newlongs.length; n++)
		for(int f=0; f<formatos.length; f++)
			{
			mutante[n][f] = ran * mutante[n][f] + (1.0f - ran) * original[n][f];
			}
		}


	// Creo una distribucion a partir de otras dos (cruce).
	private static float[][] cruzarDistribuciones(float[][] padre, float[][] madre)
		{
		float[][] hijo = new float[newlongs.length][formatos.length];
		cruzarDistribuciones(hijo, padre, madre);
		return hijo;
		}

	private static void cruzarDistribuciones(float[][] hijo, float[][] padre, float[][] madre)
		{
		for(int n=0; n<newlongs.length; n++)
		for(int f=0; f<formatos.length; f++)
			{
			float ran = (float) Math.random();
			hijo[n][f] = ran * padre[n][f] + (1.0f - ran) * madre[n][f];
			}
		}


	@Override
	public String toString()
		{
		String res = "";

		for(int n=0; n<newlongs.length; n++)
			{
			if(n != 0) res += "\n";
			res += "Newlong " + newlongs[n].getId() + ": ";

			for(int f=0; f<formatos.length; f++)
				{
				if(f != 0) res += " - ";
				res += distribucion[n][f];
				}
			}

		return res;
		}

	public String toJson()
		{
		String res = "[";
		int k = 0;

		for(int n=0; n<newlongs.length; n++)
		for(int f=0; f<formatos.length; f++)
			{
			if(capacidades[n][f])
				{
				if(k != 0) res +=",";

				res += " { ";
				res += " \"newlong\" : \"" + newlongs[n].getId() + "\", ";
				res += " \"formato\" : \"" + formatos[f].getId() + "\", ";
				res += " \"valor\" : \"" + distribucion[n][f] + "\"";
				res += " } ";

				k++;
				}
			}

		return res + "]";
		}

	// --------------------------- //
	// --------- Pruebas --------- //
	// --------------------------- //

	public static void main(String[] args)
		{
		Distribucion d1 = new Distribucion();
		Distribucion d2 = new Distribucion();
		Distribucion d3 = new Distribucion();

		System.out.println(d1.getFitness());
		d1.rehacer();
		System.out.println(d1.getFitness());
		}
	}
